﻿<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Servo Layout Debugger</title>

        <!-- Bootstrap -->
        <link href="css/bootstrap.min.css" rel="stylesheet">

        <!-- Treeview -->
        <link href="css/bootstrap-treeview.min.css" rel="stylesheet">

        <!-- JSDiffPatch -->
        <link href="css/formatters/html.css" rel="stylesheet">

        <!-- Custom -->
        <link href="css/main.css" rel="stylesheet">

        <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
          <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
        <![endif]-->
    </head>
    <body>
        <div class="container" role="main">
            <div class="row">
                <div class="col-sm-12">
                  <h1> Servo Layout Viewer </h1>
                  <p> Check the <a href="https://github.com/servo/servo/blob/main/etc/layout_viewer/README">README</a> for instructions.</p>
                </div>
            </div>
            <div class="row">
                <div class="col-sm-4">
                    <div class="row">
                        <div class="col-sm-12">
                            <div class="well">
                                <input type=file>
                            </div>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-sm-12">
                            <div id="trace-tree">
                            </div>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-sm-12">
                            <ul id="trace-list" class="list-group">
                            </ul>
                        </div>
                    </div>
                </div>
                <div class="col-sm-8">
                    <div class="row">
                        <div class="col-sm-12">
                            <div class='panel panel-default'>
                                <div class='panel-heading'>Flow Tree</div>
                                <div class='panel-body' id="flow-tree"></div>
                            </div>
                        </div>
                        <div class="col-sm-12">
                            <div id="flow-diffs"></div>
                        </div>
                    </div>
                </div>
            </div>
            <div id="toolbar">
              <a href="#" id="prev_trace">< Prev step</a>
              |
              <a href="#" id="next_trace">Next step ></a>
              <br>
              <input type="checkbox" name="show_unchanged" id="show_unchanged" />
              <label for="show_unchanged">Show unchanged code</label>
              <br>
              <a href="#top">Back to top</a>
            </div>
        </div>

        <!-- jQuery -->
        <script src="js/jquery.min.js"></script>
        <!-- Bootstrap -->
        <script src="js/bootstrap.min.js"></script>
        <!-- Treeview -->
        <script src="js/bootstrap-treeview.min.js"></script>
        <!-- JSDiffPatch -->
        <script src="js/bundle.min.js"></script>
        <script src="js/formatters.min.js"></script>

        <script>
            function get_base(trace_node) {
                if (typeof(trace_node.data.base) == "undefined" && typeof(trace_node.data.block_flow) != "undefined") {
                  return trace_node.data.block_flow.base;
                }
                else {
                  return trace_node.data.base;
                }
            }

            function create_flow_tree(trace_node) {
                var base = get_base(trace_node);

                var node = {
                    text: trace_node.class + " (" + base.id + ")",
                    id: base.id,
                    icon: "dummy",
                    href: "#diff-" + base.id
                };

                var children = [];
                for (var i=0 ; i < base.children.length ; ++i) {
                    children.push(create_flow_tree(base.children[i]));
                }

                if (children.length > 0) {
                    node.nodes = children;
                }

                return node;
            }

            function create_flow_hash(trace_node, flow_hash) {
                var base = get_base(trace_node);
                flow_hash[base.id] = trace_node;

                for (var i=0 ; i < base.children.length ; ++i) {
                    create_flow_hash(base.children[i], flow_hash);
                }

                delete base.children;
            }

            function flatten_trace(trace_node) {
                var flow_tree = create_flow_tree(trace_node.children[0]);

                var flow_hash = {};
                create_flow_hash(trace_node.children[0], flow_hash);

                return {
                    tree: flow_tree,
                    flows: flow_hash,
                }
            }

            function create_tree_node(trace_node) {
                var pre_trace = flatten_trace(trace_node.pre);
                var post_trace = flatten_trace(trace_node.post);

                var tree_node = {
                    text: trace_node.name,
                    icon: "dummy",
                    flow_tree: pre_trace.tree,      // assume pre/post trace always have same tree!
                    pre: pre_trace.flows,
                    post: post_trace.flows,
                };

                var children = [];

                for (var i=0 ; i < trace_node.children.length ; ++i) {
                    children.push(create_tree_node(trace_node.children[i]));
                }

                if (children.length > 0) {
                    tree_node.nodes = children;
                }

                return tree_node;
            }

            function update_flow_tree_bgcolor(flow_tree_node, node_color_hash) {
                flow_tree_node.backColor = node_color_hash[flow_tree_node.id];
                if (flow_tree_node.nodes !== undefined) {
                    for (var i=0 ; i < flow_tree_node.nodes.length ; ++i) {
                        update_flow_tree_bgcolor(flow_tree_node.nodes[i], node_color_hash)
                    }
                }
            }

            function new_data_loaded(data) {
                jsondiffpatch.formatters.html.hideUnchanged();

                var node_color_hash = {};
                var tree = [ create_tree_node(data) ];
                $('#trace-tree').treeview({data: tree, levels: 3});
                $('#trace-tree').on('nodeSelected', function(event, node) {
                    $("#flow-diffs").empty();
                    $('#trace-tree').treeview(true).revealNode(node);

                    for (var key in node.pre) {
                        var flow_left = node.pre[key];
                        var flow_right = node.post[key];

                        var delta = jsondiffpatch.create({
                            objectHash: function(obj) {
                                if (obj.data !== undefined && obj.data.base !== undefined) {
                                    return obj.data.base.id;
                                }
                                if (obj.id !== undefined) {
                                    return obj.id;
                                }
                                if (obj.index !== undefined) {
                                    // FlexItem and FlexLine
                                    return obj.index;
                                }
                                return JSON.stringify(obj);
                            }
                        }).diff(flow_left, flow_right);

                        if (delta !== undefined) {
                            var diff_id = "diff-" + key;
                            $("#flow-diffs").append(
                              "<div class='panel panel-default' id='" +
                              diff_id +
                              "'><div class='panel-heading'>" +
                              flow_left.class + " (" + key + ")" +
                              "</div><div class='panel-body'></div></div>");

                            document.getElementById(diff_id).getElementsByClassName('panel-body')[0].innerHTML =
                                jsondiffpatch.formatters.html.format(delta, flow_left);
                            node_color_hash[key] = "rgba(255, 0, 0, 0.7)";
                        } else {
                            node_color_hash[key] = "rgb(212, 248, 212)";
                        }
                    }

                    update_flow_tree_bgcolor(node.flow_tree, node_color_hash);
                    $('#flow-tree').treeview({data: [node.flow_tree], levels: 100, enableLinks: true, emptyIcon: "glyphicon glyphicon-unchecked hidden-glyphicon"});
                });

                $('#trace-tree').treeview(true).selectNode(0);
            }

            function register_toggle_unchanaged_code_handler() {
              var show_unchange_box = document.getElementById("show_unchanged");
              show_unchange_box.addEventListener("change", function(evt){
                  jsondiffpatch.formatters.html.showUnchanged(show_unchange_box.checked, null, 800);
              });
            }

            function register_prev_next_trace_node() {
              var prev_btn = document.getElementById("prev_trace");
              var next_btn = document.getElementById("next_trace");
              prev_btn.addEventListener("click", function(evt){
                var node_id = $("#trace-tree").treeview(true).getSelected()[0].nodeId;
                // We deliberatly choose to ignore the node_id out of bound case,
                // since it won't break the UI usability
                $("#trace-tree").treeview(true).selectNode(node_id - 1);
              });
              next_btn.addEventListener("click", function(evt){
                var node_id = $("#trace-tree").treeview(true).getSelected()[0].nodeId;
                $("#trace-tree").treeview(true).selectNode(node_id + 1);
              });
            }

            $( document ).ready(function() {
                var upload = document.getElementsByTagName('input')[0];
                upload.onchange = function (e) {
                    e.preventDefault();

                    var file = upload.files[0],
                    reader = new FileReader();
                    reader.onload = function (event) {
                        new_data_loaded(JSON.parse(event.target.result));
                    };

                    reader.readAsText(file);
                    return false;
                };
                register_toggle_unchanaged_code_handler();
                register_prev_next_trace_node();
            });
        </script>
    </body>
</html>
